package com.itheima.spring.beans;

import com.itheima.spring.aop.aware.BeanFactoryAware;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 实现一个默认的bean工厂DefaultBeanFactory来创建bean实例了,DefaultBeanFactory除了需要实现BeanFactory外,
 * 还需要实现Bean定义注册接口BeanDefinitionRegistry,因为要把bean定义注册到bean工厂里面
 */
public class DefaultBeanFactory implements BeanFactory,BeanDefinitionRegistry, Closeable {

    private final Log logger = LogFactory.getLog(getClass());

    /**
     * 用Map来存放bean定义信息
     * key:beanName
     * value:BeanDefinition
     */
    private Map<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

    /**
     * 用Map来存放创建的bean实例,注意这里只是存放单例bean,多实例每次都要创建新的,不需要存放
     */
    private Map<String,Object> beanMap = new ConcurrentHashMap<>(256);


    /**
     * 记录正在构造的bean
     * 构造实例对象时的循环依赖,会陷入僵死局面,是不允许构造实例时的循环依赖的。
     * 那么怎么发现循环依赖呢?
     *
     * 发现循环依赖的方法：加入一个正在构造的bean的记录,每个bean开始构造时加入该记录中,构造完成后从记录中移除。
     * 如果有依赖,先看依赖的bean是否在构造中,如是就构成了循环依赖,抛出异常
     */
    private ThreadLocal<Set<String>> buildingBeans = new ThreadLocal<>();

    private List<BeanPostProcessor> beanPostProcessors = Collections.synchronizedList(new ArrayList<>());


    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionRegisterException {

        //(1)判断给入的beanName和beanDefinition不能为空
        Objects.requireNonNull(beanName,"beanName is not null.");
        Objects.requireNonNull(beanDefinition,"beanDefinition is not null.");

        //(2)检查给的bean是否合法
        if(!beanDefinition.validate()){
            throw new BeanDefinitionRegisterException("名字为[" + beanName + "] 的bean定义不合法：" + beanDefinition);
        }
        //(3)如果已存在bean定义就抛异常
        if(this.containsBeanDefinition(beanName)){
            throw new BeanDefinitionRegisterException(
                    "名字为[" + beanName + "] 的bean定义已存在:" + this.getBeanDefinition(beanName));
        }

        //(4)把bean定义放到Map里面
        beanDefinitionMap.put(beanName,beanDefinition);

    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        //根据bean的名字从Map里面获取bean定义
        return this.beanDefinitionMap.get(beanName);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        //根据bean的名字判断Map里面是否包含bean定义
        return this.beanDefinitionMap.containsKey(beanName);
    }

    /**
     * //根据bean的名字获取bean实例,里面主要做的工作是创建bean实例和对bean实例进行初始化
     * @param name 通过bean name获取bean
     * @return
     * @throws Exception
     */
    @Override
    public Object getBean(String name) throws Throwable {
        return this.doGetBean(name);
    }

    @Override
    public void registerBeanPostProcessor(BeanPostProcessor bpp) {
        this.beanPostProcessors.add(bpp);
        if(bpp instanceof BeanFactoryAware){
            ((BeanFactoryAware) bpp).setBeanFactory(this);
        }

    }

    //根据bean的名字获取bean实例,里面主要做的工作是创建bean实例和对bean实例进行初始化
    protected Object doGetBean(String name) throws Throwable{
        //Bean name 不能为空
        Objects.requireNonNull(name,"bean name is not null.");
        //先从beanMap里面获取bean实例
        Object instance  = this.beanMap.get(name);

        //如果beanMap里面已存在bean实例就直接返回,不需要走后面的流程了
        if (instance != null){
            return instance;
        }

        //从beanDefintionMap里面获取bean定义信息
        BeanDefinition bd = this.beanDefinitionMap.get(name);
        //bean定义信息不能为空
        Objects.requireNonNull(bd, "不存在name为：" + name + "beean 定义！");

        //记录正在创建的bean
        Set<String> ingBeans = this.buildingBeans.get();
        if(ingBeans == null){
            ingBeans = new HashSet<String>();
            this.buildingBeans.set(ingBeans);
        }

        //检测循环依赖 因为如果已bean正在构造,这时发现有其他的bean依赖它,此时它又没有创建好就需要抛异常了
        if(ingBeans.contains(name)){
            throw new Exception(name + " 循环依赖！" + ingBeans);
        }

        // 记录正在创建的Bean
        ingBeans.add(name);

        //获取bean类型
        Class<?> type = bd.getBeanClass();
        if(type != null){
            //如果bean的类型不为空,并且工厂方法名为空,说明是使用构造方法的方式来创建bean实例
            if(StringUtils.isBlank(bd.getFactoryMethodName())){
                // 构造方法来构造对象
                instance = this.createInstanceByConstructor(bd);
            }else {
                //如果bean的类型不为空,并且工厂方法名不为空,说明是使用静态工厂方法的方式来创建bean实例
                // 静态工厂方法
                instance = this.createInstanceByStaticFactoryMethod(bd);
            }
        }else{
            //如果bean的类型为空,说明是使用工厂bean的方式来创建bean实例
            // 工厂bean方式来构造对象
            instance = this.createInstanceByFactoryBean(bd);
        }

        //创建好实例后，移除创建中记录
        ingBeans.remove(name);

        // 给入属性依赖
        this.setPropertyDIValues(bd, instance);

        // 应用bean初始化前的处理
        instance = this.applyPostProcessBeforeInitialization(instance, name);

        // 执行初始化方法
        this.doInit(bd, instance);

        // 应用bean初始化后的处理
        instance = this.applyPostProcessAfterInitialization(instance, name);


        //存放单例的bean到beanMap
        if (bd.isSingleton()){
            beanMap.put(name,instance);
        }
        return instance;
    }

    // 应用bean初始化前的处理
    private Object applyPostProcessBeforeInitialization(Object bean,String beanName) throws Throwable{
        for (BeanPostProcessor bpp:this.beanPostProcessors){
           bean = bpp.postProcessBeforeInitialization(bean,beanName);
        }
        return bean;
    }

    //应用bean初始化后的处理
    private Object applyPostProcessAfterInitialization(Object bean,String beanName) throws Throwable{
        for (BeanPostProcessor bpp:this.beanPostProcessors){
            bean = bpp.postProcessAfterInitialization(bean,beanName);
        }
        return bean;
    }

    private void doInit(BeanDefinition bd, Object instance) throws Exception {
        // 获取bean定义中的初始化方法,如果存在初始化方法就通过反射去执行初始化方法
        if(StringUtils.isNotBlank(bd.getInitMethodName())){
            Method method = instance.getClass().getMethod(bd.getInitMethodName(), null);
            method.invoke(instance,null);
        }

    }

    // 工厂bean方式来构造对象 反射
    private Object createInstanceByFactoryBean(BeanDefinition bd) throws Throwable {
        //通过bean定义信息中工厂bean的名字获取工厂bean的实例
        Object factoryBean  = this.doGetBean(bd.getFactoryBeanName());
        //通过bean定义信息中工厂方法的名字反射出工厂bean的方法m创建bean实例
        Class<?> beanClass = factoryBean.getClass();
        Object[] realValues = this.getRealValues(bd.getConstructorArgumentValues());
        Method method = this.determineFactoryMethod(bd, realValues, beanClass);
        //Method method = factoryBean.getClass().getMethod(bd.getFactoryMethodName(), null);
        return method.invoke(factoryBean,realValues);
    }

    // 静态工厂方法 反射
    private Object createInstanceByStaticFactoryMethod(BeanDefinition bd) throws Throwable {
        //获取bean类型
        Class<?> type = bd.getBeanClass();
        Object[] realValues = this.getRealValues(bd.getConstructorArgumentValues());
        Method method = this.determineFactoryMethod(bd, realValues, null);
        /*//通过静态工厂方法方法的名字getFactoryMethodName反射出bean的方法m创建bean实例
        Method method = type.getMethod(bd.getFactoryMethodName(), null);*/
        return method.invoke(type,realValues);
    }

    //构造方法来构造对象 反射
    private Object createInstanceByConstructor(BeanDefinition bd) throws Throwable {
        try {
            //获取bean定义中的构造参数
            Object[] args = this.getConstructorArgumentValues(bd);
            if(args == null){
                //拿到bean的类型,然后调用newInstance通过反射来创建bean实例
                return bd.getBeanClass().newInstance();
            }else {
                bd.setConstructorArgumentRealValues(args);

                Constructor<?> constructor = this.determineConstructor(bd,args);
                // 缓存构造函数由determineConstructor 中移到了这里,无论原型否都缓存
                // 因为后面AOP需要用
                bd.setConstructor(constructor);
                //查找有构造参数并返回
                return constructor.newInstance(args);
            }

        }catch (Exception e){
            logger.error("创建bean的实例异常,beanDefinition：" + bd, e);
            throw e;
        }
    }

    //把bean定义中的构造参数引用转为真实的值
    private Object[] getConstructorArgumentValues(BeanDefinition bd) throws Throwable{
        return this.getRealValues(bd.getConstructorArgumentValues());
    }

    //把bean定义中的构造参数引用转为真实的值
    private Object[] getRealValues(List<?> defs) throws Throwable {
        if(CollectionUtils.isEmpty(defs)){
            return null;
        }
        Object[] values = new Object[defs.size()];
        int i = 0;
        Object v = null;
        for (Object rv:defs){
            if(rv == null){
                v = null;
            }else if (rv instanceof BeanReference){
                v = this.doGetBean(((BeanReference) rv).getBeanName());
            }else if (rv instanceof Object[]){
                // TODO 处理集合中的bean引用
            }else if (rv instanceof Collection){
                // TODO 处理集合中的bean引用

            }else if (rv instanceof Properties){
                // TODO 处理properties中的bean引用

            }else if(rv instanceof Map){
                // TODO 处理Map中的bean引用

            }else{
                v = rv;
            }
            values[i++] = v;
        }
        return values;
    }

    /**
     * 查找构造参数的方法
     * @param bd
     * @param args
     * @return
     */
    private Constructor<?> determineConstructor(BeanDefinition bd,Object[] args) throws Throwable{

        Constructor<?> ct = null;

        //如果参数为空,则返回无参的构造方法
        if(args == null){
             return bd.getBeanClass().getConstructor(null);
        }

        //对于原型bean,从第二次开始获取bean实例时，可直接获得第一次缓存的构造方法。
        ct = bd.getConstructor();
        if(ct != null){
            return ct;
        }

        //先根据参数类型获取精确匹配的构造方法
        Class<?>[] paramTypes = new Class[args.length];
        int j=0;
        for (Object o : args){
            paramTypes[j++] = o.getClass();
        }
        try {
            ct = bd.getBeanClass().getConstructor(paramTypes);
        }catch (Exception e){

        }

        //如果根据参数类型进行精确批次查找没有找到构造方法,就获得所有的构造方法遍历，
        //通过参数数量过滤，再比对形参类型与实参类型
        if (null == ct){
            //没有精确参数类型匹配的，则遍历匹配所有的构造方法
            //判断逻辑：先判断参数数量，再依次比对形参类型与实参类型
            outer: for (Constructor<?> ct0: bd.getBeanClass().getConstructors()){
                Class<?>[] parameterTypes = ct0.getParameterTypes();
                if (parameterTypes.length == args.length){
                    for (int i =0;i< parameterTypes.length;i++){
                        //isAssignableFrom方法表示是否可以把args[i].getClass()赋值给paramterTypes[i]
                        if(!parameterTypes[i].isAssignableFrom(args[i].getClass())){
                            continue outer;
                        }
                    }
                    ct = ct0;
                    break outer;
                }
            }
        }

        //如果找到构造方法了，并且是原型的就缓存起来
        if (ct != null){
            // 对于原型bean,可以缓存找到的构造方法，方便下次构造实例对象。
            // 在BeanDefinfition中获取设置所用构造方法的方法。
            // 同时在上面增加从beanDefinition中获取的逻辑
            if (bd.isPrototype()){
                bd.setConstructor(ct);
            }
            return ct;

        }else {
            throw new Exception("不存在对应的构造方法！" + bd);
        }
    }

    /**
     * 查找工厂方法的方法
     * @return
     */
    private Method determineFactoryMethod(BeanDefinition bd,Object[] args,Class<?> type) throws Throwable{

        if(type == null){
            type = bd.getBeanClass();
        }

        //获取bean定义中工厂的名字
        String methodName = bd.getFactoryMethodName();

        //如果参数为空就返回无参的方法
        if(args == null){
            return type.getMethod(methodName,null);
        }

        Method m = null;
        //对于原型bean,从第二次开始获取bean实例时，可直接获得第一次缓存的构造方法
        m = bd.getFactoryMethod();
        if (m != null){
            return m;
        }

        //先根据参数类型获取精确匹配的方法
        Class<?>[] paramTypes = new Class[args.length];
        int j = 0;

        for (Object o: args){
            paramTypes[j++] = o.getClass();
        }

        try{
            m = type.getMethod(methodName, paramTypes);
        }catch (Exception e){

        }

        //如果根据参数类型进行精确批次查找没有找到工厂方法,就获得所有的构造方法遍历
        //通过参数数量过滤，再比对形参类型与实参类型
        if (m == null) {
            // 没有精确参数类型匹配的，则遍历匹配所有的方法
            // 判断逻辑：先判断参数数量，再依次比对形参类型与实参类型
            outer:for (Method m0: type.getMethods()){
                if(paramTypes.length == args.length){
                    for (int i = 0; i < paramTypes.length; i++) {
                        //isAssignableFrom方法表示是否可以把args[i].getClass()赋值给paramterTypes[i]
                        if (!paramTypes[i].isAssignableFrom(args[i].getClass())) {
                            continue outer;
                        }
                    }
                    m  = m0;
                    break outer;
                }
            }
        }
        //如果找到构造方法了，并且是原型的就缓存起来
        if(m != null){
            // 对于原型bean,可以缓存找到的方法，方便下次构造实例对象。
            // 在BeanDefinfition中获取设置所用方法的方法。
            // 同时在上面增加从beanDefinition中获取的逻辑。
            if(bd.isPrototype()){
                bd.setFactoryMethod(m);
            }
            return m;
        }else{
            throw new Exception("不存在对应的构造方法！" + bd);
        }
    }

    /**
     * 属性依赖实现
     * @param bd
     * @param instance
     * @throws Exception
     */
    private void setPropertyDIValues(BeanDefinition bd, Object instance) throws Throwable {

        //bean定义中没有属性依赖就直接返回
        if (CollectionUtils.isEmpty(bd.getPropertyValues())){
            return;
        }

        //如果bean定义中有属性依赖就设置值,设置方式和构造参数的设置一样
        for (PropertyValue pv: bd.getPropertyValues()){
            //属性依赖没有给名字就直接跳过
            if(StringUtils.isBlank(pv.getName())){
                continue;
            }
            //获取类对象
            Class<?> clazz = instance.getClass();
            //通过属性名获取类对象里面声明的字段
            Field field = clazz.getDeclaredField(pv.getName());
            //设置字段可访问
            field.setAccessible(true);

            //把属性值转化为真正的值,和构造参数一样
            Object rv = pv.getValue();
            Object v = null;
            if(rv == null){
                v = null;
            }else if (rv instanceof BeanReference){
                v = this.doGetBean(((BeanReference) rv).getBeanName());
            }else if (rv instanceof Object[]){
                // TODO 处理集合中的bean引用
            }else if (rv instanceof Collection){
                // TODO 处理集合中的bean引用
            }else if(rv instanceof Properties){
                //TODO 处理properties中的bean引用
            }else if(rv instanceof Map){
                //TODO 处理Map中的bean引用
            }else {
                v = rv;
            }

            //把真正的值v设置到属性p里面 即属性p依赖的值v
            field.set(instance, v);
        }

    }

    /**
     * 销毁
     * @throws IOException
     */
    @Override
    public void close() throws IOException {
        // 执行单例实例的销毁方法
        for (Map.Entry<String, BeanDefinition> entry : this.beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            try {
                //获取bean定义中的销毁方法,如果存在销毁方法就通过反射去执行销毁方法
                if(beanDefinition.isSingleton() && StringUtils.isNotBlank(beanDefinition.getDestroyMethodName())){
                    //获取bean实例
                    Object instance = this.beanMap.get(beanName);
                    Method method = instance.getClass().getMethod(beanDefinition.getDestroyMethodName(), null);
                    method.invoke(instance,null);
                }
            }catch (Exception e){
                logger.error("执行bean[" + beanName + "] " + beanDefinition + " 的 销毁方法异常！", e);
            }
        }
    }
}
